/*
 * pg_db_role_setting.c
 *		支持操作 pg_db_role_setting 关系的例程
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 * IDENTIFICATION
 *		src/backend/catalog/pg_db_role_setting.c
 */
#include "postgres.h"

#include "access/genam.h"
#include "access/heapam.h"
#include "access/htup_details.h"
#include "access/tableam.h"
#include "catalog/indexing.h"
#include "catalog/objectaccess.h"
#include "catalog/pg_db_role_setting.h"
#include "utils/fmgroids.h"
#include "utils/rel.h"

void AlterSetting(Oid fc_databaseid, Oid fc_roleid, VariableSetStmt *fc_setstmt)
{
	char	   *fc_valuestr;
	HeapTuple	fc_tuple;
	Relation	fc_rel;
	ScanKeyData fc_scankey[2];
	SysScanDesc fc_scan;

	fc_valuestr = ExtractSetVariableArgs(fc_setstmt);

	/* 获取旧元组（如果有）。 */

	fc_rel = table_open(DbRoleSettingRelationId, RowExclusiveLock);
	ScanKeyInit(&fc_scankey[0],
				Anum_pg_db_role_setting_setdatabase,
				BTEqualStrategyNumber, F_OIDEQ,
				ObjectIdGetDatum(fc_databaseid));
	ScanKeyInit(&fc_scankey[1],
				Anum_pg_db_role_setting_setrole,
				BTEqualStrategyNumber, F_OIDEQ,
				ObjectIdGetDatum(fc_roleid));
	fc_scan = systable_beginscan(fc_rel, DbRoleSettingDatidRolidIndexId, true,
							  NULL, 2, fc_scankey);
	fc_tuple = systable_getnext(fc_scan);

	/*
	 * 有三种情况：
	 *
	 * - 在 RESET ALL 中，请求 GUC 重置设置数组并更新目录，如果还有剩余，则删除，否则删除它
	 *
	 * - 在其他命令中，如果 pg_db_role_setting 中有元组，更新它；如果最终为空，则删除它
	 *
	 * - 否则，插入一个新的 pg_db_role_setting 元组，但仅当命令不是 RESET 时
	 */
	if (fc_setstmt->kind == VAR_RESET_ALL)
	{
		if (HeapTupleIsValid(fc_tuple))
		{
			ArrayType  *fc_new = NULL;
			Datum		fc_datum;
			bool		fc_isnull;

			fc_datum = heap_getattr(fc_tuple, Anum_pg_db_role_setting_setconfig,
								 RelationGetDescr(fc_rel), &fc_isnull);

			if (!fc_isnull)
				fc_new = GUCArrayReset(DatumGetArrayTypeP(fc_datum));

			if (fc_new)
			{
				Datum		fc_repl_val[Natts_pg_db_role_setting];
				bool		fc_repl_null[Natts_pg_db_role_setting];
				bool		fc_repl_repl[Natts_pg_db_role_setting];
				HeapTuple	fc_newtuple;

				memset(fc_repl_repl, false, sizeof(fc_repl_repl));

				fc_repl_val[Anum_pg_db_role_setting_setconfig - 1] =
					PointerGetDatum(fc_new);
				fc_repl_repl[Anum_pg_db_role_setting_setconfig - 1] = true;
				fc_repl_null[Anum_pg_db_role_setting_setconfig - 1] = false;

				fc_newtuple = heap_modify_tuple(fc_tuple, RelationGetDescr(fc_rel),
											 fc_repl_val, fc_repl_null, fc_repl_repl);
				CatalogTupleUpdate(fc_rel, &fc_tuple->t_self, fc_newtuple);
			}
			else
				CatalogTupleDelete(fc_rel, &fc_tuple->t_self);
		}
	}
	else if (HeapTupleIsValid(fc_tuple))
	{
		Datum		fc_repl_val[Natts_pg_db_role_setting];
		bool		fc_repl_null[Natts_pg_db_role_setting];
		bool		fc_repl_repl[Natts_pg_db_role_setting];
		HeapTuple	fc_newtuple;
		Datum		fc_datum;
		bool		fc_isnull;
		ArrayType  *fc_a;

		memset(fc_repl_repl, false, sizeof(fc_repl_repl));
		fc_repl_repl[Anum_pg_db_role_setting_setconfig - 1] = true;
		fc_repl_null[Anum_pg_db_role_setting_setconfig - 1] = false;

		/* 提取 setconfig 的旧值 */
		fc_datum = heap_getattr(fc_tuple, Anum_pg_db_role_setting_setconfig,
							 RelationGetDescr(fc_rel), &fc_isnull);
		fc_a = fc_isnull ? NULL : DatumGetArrayTypeP(fc_datum);

		/* 更新（在 RESET 情况下，valuestr 为 NULL） */
		if (fc_valuestr)
			fc_a = GUCArrayAdd(fc_a, fc_setstmt->name, fc_valuestr);
		else
			fc_a = GUCArrayDelete(fc_a, fc_setstmt->name);

		if (fc_a)
		{
			fc_repl_val[Anum_pg_db_role_setting_setconfig - 1] =
				PointerGetDatum(fc_a);

			fc_newtuple = heap_modify_tuple(fc_tuple, RelationGetDescr(fc_rel),
										 fc_repl_val, fc_repl_null, fc_repl_repl);
			CatalogTupleUpdate(fc_rel, &fc_tuple->t_self, fc_newtuple);
		}
		else
			CatalogTupleDelete(fc_rel, &fc_tuple->t_self);
	}
	else if (fc_valuestr)
	{
		/* 非空的valuestr意味着不是RESET，因此插入一个新元组 */
		HeapTuple	fc_newtuple;
		Datum		fc_values[Natts_pg_db_role_setting];
		bool		fc_nulls[Natts_pg_db_role_setting];
		ArrayType  *fc_a;

		memset(fc_nulls, false, sizeof(fc_nulls));

		fc_a = GUCArrayAdd(NULL, fc_setstmt->name, fc_valuestr);

		fc_values[Anum_pg_db_role_setting_setdatabase - 1] =
			ObjectIdGetDatum(fc_databaseid);
		fc_values[Anum_pg_db_role_setting_setrole - 1] = ObjectIdGetDatum(fc_roleid);
		fc_values[Anum_pg_db_role_setting_setconfig - 1] = PointerGetDatum(fc_a);
		fc_newtuple = heap_form_tuple(RelationGetDescr(fc_rel), fc_values, fc_nulls);

		CatalogTupleInsert(fc_rel, fc_newtuple);
	}

	InvokeObjectPostAlterHookArg(DbRoleSettingRelationId,
								 fc_databaseid, 0, fc_roleid, false);

	systable_endscan(fc_scan);

	/* 关闭pg_db_role_setting，但在提交之前保持锁定 */
	table_close(fc_rel, NoLock);
}

/*
 * 从目录中删除一些设置。这些可以是特定数据库的，或特定角色的。（当然也可以同时处理两者，但对于当前用途没有意义。）
 */
void DropSetting(Oid fc_databaseid, Oid fc_roleid)
{
	Relation	fc_relsetting;
	TableScanDesc fc_scan;
	ScanKeyData fc_keys[2];
	HeapTuple	fc_tup;
	int			fc_numkeys = 0;

	fc_relsetting = table_open(DbRoleSettingRelationId, RowExclusiveLock);

	if (OidIsValid(fc_databaseid))
	{
		ScanKeyInit(&fc_keys[fc_numkeys],
					Anum_pg_db_role_setting_setdatabase,
					BTEqualStrategyNumber,
					F_OIDEQ,
					ObjectIdGetDatum(fc_databaseid));
		fc_numkeys++;
	}
	if (OidIsValid(fc_roleid))
	{
		ScanKeyInit(&fc_keys[fc_numkeys],
					Anum_pg_db_role_setting_setrole,
					BTEqualStrategyNumber,
					F_OIDEQ,
					ObjectIdGetDatum(fc_roleid));
		fc_numkeys++;
	}

	fc_scan = table_beginscan_catalog(fc_relsetting, fc_numkeys, fc_keys);
	while (HeapTupleIsValid(fc_tup = heap_getnext(fc_scan, ForwardScanDirection)))
	{
		CatalogTupleDelete(fc_relsetting, &fc_tup->t_self);
	}
	table_endscan(fc_scan);

	table_close(fc_relsetting, RowExclusiveLock);
}

/*
 * 扫描pg_db_role_setting，寻找适用的设置，并加载到当前进程中。
 *
 * relsetting是pg_db_role_setting，已经打开并锁定。
 *
 * 注意：我们只考虑确切的databaseid/roleid组合的设置。
 * 这可能需要多次调用，并传入InvalidOid作为databaseid/roleid。
 */
void ApplySetting(Snapshot fc_snapshot, Oid fc_databaseid, Oid fc_roleid,
			 Relation fc_relsetting, GucSource fc_source)
{
	SysScanDesc fc_scan;
	ScanKeyData fc_keys[2];
	HeapTuple	fc_tup;

	ScanKeyInit(&fc_keys[0],
				Anum_pg_db_role_setting_setdatabase,
				BTEqualStrategyNumber,
				F_OIDEQ,
				ObjectIdGetDatum(fc_databaseid));
	ScanKeyInit(&fc_keys[1],
				Anum_pg_db_role_setting_setrole,
				BTEqualStrategyNumber,
				F_OIDEQ,
				ObjectIdGetDatum(fc_roleid));

	fc_scan = systable_beginscan(fc_relsetting, DbRoleSettingDatidRolidIndexId, true,
							  fc_snapshot, 2, fc_keys);
	while (HeapTupleIsValid(fc_tup = systable_getnext(fc_scan)))
	{
		bool		fc_isnull;
		Datum		fc_datum;

		fc_datum = heap_getattr(fc_tup, Anum_pg_db_role_setting_setconfig,
							 RelationGetDescr(fc_relsetting), &fc_isnull);
		if (!fc_isnull)
		{
			ArrayType  *fc_a = DatumGetArrayTypeP(fc_datum);

			/*
			 * 我们在SUSET级别处理所有选项。我们假设插入pg_db_role_setting的权限在插入时已被检查。
			 */
			ProcessGUCArray(fc_a, PGC_SUSET, fc_source, GUC_ACTION_SET);
		}
	}

	systable_endscan(fc_scan);
}
